/**
 * 
 */
package group.anglers.om.core.persistence.impls;

import group.anglers.om.core.exceptions.ModelException;
import group.anglers.om.core.exceptions.OmException;
import group.anglers.om.core.exceptions.UnExpectedException;
import group.anglers.om.core.persistence.Query;
import group.anglers.om.core.persistence.serialization.Column;
import group.anglers.om.core.persistence.serialization.Composite;
import group.anglers.om.core.persistence.serialization.impl.ModelSerialization;
import group.anglers.om.core.persistence.serialization.impl.ModelSerializationUtil;

import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;

/**
 * @author an.pham
 *
 */
class SqlUtils {
	/**
	 * 
	 * @param resultSet
	 * @param columnName
	 * @param type
	 * @return
	 * @throws SQLException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 */
	public static Object getObject(ResultSet resultSet, String columnName, Class<?> type) throws SQLException
		, InstantiationException, IllegalAccessException, IllegalArgumentException
		, InvocationTargetException, NoSuchMethodException, SecurityException {
		try{
			if(type.equals(java.util.Date.class) || type.equals(java.sql.Date.class)){
				Timestamp timestamp = resultSet.getTimestamp(columnName);
				
				if(timestamp != null){
					return type.getConstructor(long.class).newInstance(timestamp.getTime());
				}
				
				return null;
			}else {
				return resultSet.getObject(columnName);
			}
		}catch(SQLException e){
			if(!e.getSQLState().equals("S0022")){
				throw e;
			}
			return null;
		}
		
	}
	/**
	 * 
	 * @param columns
	 * @param model
	 * @param modelClass
	 * @param query
	 * @return
	 * @throws OmException
	 */
	@SuppressWarnings("rawtypes")
	public static Query setParameter(List<Column> columns, Object model, Class modelClass, Query query) throws OmException {
		Column column = null;
		
		int loop = columns.size();
		for(int i=0; i < loop; i ++){
			column = columns.get(i);
			query.setParameter(ModelSerializationUtil.invokeGetMethod(modelClass, column.getName(), model));
		}
		
		return query;
	}
	/**
	 * 
	 * @param primaryKey
	 * @param model
	 * @param modelClass
	 * @param query
	 * @return
	 * @throws OmException
	 */
	@SuppressWarnings("rawtypes")
	public static Query setParameter(Object primaryKey, Object model, Class modelClass, Query query) throws OmException {
		Column column = null;
		
		if(primaryKey instanceof Composite){
			Composite composite = (Composite) primaryKey;
			
			int loop = composite.getColumns().size();
			/* get primary key instance */
			Object pkInstance =  ModelSerializationUtil.invokeGetMethod(modelClass, composite.getName(), model);
			for(int i=0; i < loop; i ++){
				column = composite.getColumns().get(i);
				/* call getter method on each column inside the primary key instance */
				query.setParameter(ModelSerializationUtil.invokeGetMethod(composite.getType(), column.getName(), pkInstance));
			}
		}else {
			column = (Column) primaryKey;
			query.setParameter(ModelSerializationUtil.invokeGetMethod(modelClass, column.getName(), model));
		}
		
		return query;
	}
	/**
	 * 
	 * @param givenPrimaryKey
	 * @param primaryKey
	 * @param query
	 * @return
	 * @throws OmException
	 */
	@SuppressWarnings("rawtypes")
	public static Query setParameter(Object givenPrimaryKey, Object primaryKey, Query query) throws OmException {
		Class<? extends Object> pkClass = givenPrimaryKey.getClass();
		Class<? extends Object> modelPKClass = null;
		
		int loop = 0;
		Column column = null;
		/* get primary column */
		
		if(primaryKey instanceof Composite){
			Composite composite = (Composite) primaryKey;
			modelPKClass = composite.getType();
			
			loop = composite.getColumns().size();
			for(int i=0; i < loop; i ++){
				column = composite.getColumns().get(i);
				/* call getter method on each column inside the primary key instance */
				query.setParameter(ModelSerializationUtil.invokeGetMethod(composite.getType(), column.getName(), givenPrimaryKey));
			}
		}else {
			column = (Column) primaryKey;
			modelPKClass = column.getType();
			query.setParameter(givenPrimaryKey);
		}
		// checking if the given primaryKey is not same type with the  modelClass
		if(!pkClass.equals(modelPKClass)){
			throw new ModelException("primary key has no type \"" + modelPKClass.getName() + "\"");
		}
		
		
		return query;
	}
	
	public static Object mapToModel(ModelSerialization modelSerialization, ResultSet resultSet, Object model) throws OmException {
		try{
			int loop = 0;
			Column column = null;
			Object value = null;
			// set data for primary key field
			if(modelSerialization.getPrimaryKey() instanceof Composite){
				Composite composite = (Composite) modelSerialization.getPrimaryKey();
				/* instance composite primary key field */
				Object compositePK = composite.getType().newInstance();
				/* pass composite primary key into the instance */
				ModelSerializationUtil.invokeSetMethod(modelSerialization.getModelClass()
						, composite.getName(), model, compositePK);
				// pass loop number
				loop = composite.getColumns().size();
				/* pass data into the composite primary key field */
				for(int i=0; i < loop; i ++){
					column = composite.getColumns().get(i);
					// map data
					value = SqlUtils.getObject(resultSet, column.getName(), column.getType());
					
					if(value != null){
						ModelSerializationUtil.invokeSetMethod(composite.getType(), column.getName()
								, compositePK, value);
					}
				}
			}else {
				column = (Column) modelSerialization.getPrimaryKey();
				if(column != null){
					// map data
					value = SqlUtils.getObject(resultSet, column.getName(), column.getType());
					if(value != null){
						ModelSerializationUtil.invokeSetMethod(modelSerialization.getModelClass()
								, column.getName(), model, value);
					}
				}
			}
			// pass loop number
			loop = modelSerialization.getColumns().size();
			// set data for normal field
			for(int i=0; i < loop; i ++){
				column = modelSerialization.getColumns().get(i);
				// map data
				value = SqlUtils.getObject(resultSet, column.getName(), column.getType());
				if(value != null){
					ModelSerializationUtil.invokeSetMethod(modelSerialization.getModelClass()
							, column.getName(), model, value);	
				}
			}
		}catch(Exception e){
			throw new UnExpectedException(e);
		}
		
		return model;
	}
}
